package com.oak.interviewdemo.config.shiro.jwt;


import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.oak.interviewdemo.sys.entity.User;
import com.oak.interviewdemo.sys.entity.dto.UserDto;
import com.oak.interviewdemo.sys.mapper.UserMapper;
import com.oak.interviewdemo.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * 用户鉴权和获取授权
 *
 * @Author: guoxy
 * @Date: 2020/5/14 23:13
 * @Description:
 * @Version: 1.0
 */
@Slf4j
@Component
public class JwtRealm extends AuthorizingRealm {

    @Autowired
    @Lazy
    private UserMapper userMapper;

    /**
     * 必须重写此方法，不然Shiro会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 认证：校验用户名，错误抛出异常即可
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {

        log.info("-------------------------用户身份认证-------------------------");
        String token = (String) auth.getPrincipal();
//        String token = auth.getCredentials(); //此方法也可获取token
        if (StrUtil.isBlank(token)) {
            log.error("doGetAuthenticationInfo - token为空!");
            throw new AuthenticationException("token为空!");
        }
        UserDto user = checkToken(token);
        log.info("登录用户: 用户名:{} token:{}", user.getUsername(), token);
        //这里返回的是类似账号密码的东西，但是jwtToken都是jwt字符串。还需要一个该Realm的类名
        log.info("-------------------------用户身份认证通过-------------------------");
        return new SimpleAuthenticationInfo(user, token, getName());
    }


    /**
     * 授权：只有检测用户权限时才调用此方法
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.info("-------------------------用户授权认证-------------------------");

        if (ObjectUtil.isNull(principals)) {
            return null;
        }
        String token = principals.toString();
        String username = JwtUtil.getUsername(token);
        UserDto user = userMapper.selectByUsername(username);

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 设置用户拥有的角色集合，比如“admin,test”
        Set<String> roleSet = new HashSet<>(Arrays.asList(user.getRole().split(StrUtil.COMMA)));
        info.setRoles(roleSet);
        // 设置用户拥有的权限集合，比如“sys:role:add,sys:user:add”
        Set<String> permissionSet = new HashSet<>(Arrays.asList(user.getPermission().split(StrUtil.COMMA)));
        info.addStringPermissions(permissionSet);

        log.info("-------------------------用户授权成功-------------------------");
        return info;
    }


    /**
     * 校验token合法性
     *
     * @param token
     * @return
     */
    private UserDto checkToken(String token) {
        String username = JwtUtil.getUsername(token);
        if (StringUtils.isBlank(username)) {
            log.error("doGetAuthenticationInfo - token非法无效!!");
            throw new AuthenticationException("token非法无效!");
        }
        UserDto user = userMapper.selectByUsername(username);
        if (ObjectUtil.isNull(user)) {
            log.error("doGetAuthenticationInfo - 用户名不存在!");
            throw new AuthenticationException("用户名不存在!");
        }
        // 校验token是否有效
        boolean bool = JwtUtil.verify(token, user.getUsername(), user.getPassword());
        if (!bool) {
            log.error("doGetAuthenticationInfo - token已失效!");
            throw new AuthenticationException("token已失效!");
        }
        return user;
    }

}